package org.jsets.fastboot.util;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import com.esotericsoftware.reflectasm.MethodAccess;
import cn.hutool.core.util.ReflectUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ReflectUtils {

	private static final Map<String, Method> METHODS = new HashMap<String, Method>();
	private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

	public static boolean isStatic(int mod) {
		return (mod & Modifier.STATIC) != 0;
	}

	@SneakyThrows
	public static PropertyDescriptor getPropertyDescriptor(String fieldName, Class<?> clazz) {
		PropertyDescriptor pd = new PropertyDescriptor(fieldName, clazz);
		return pd;
	}

	public static Method getReadMethod(Class<?> clazz, String fieldName, boolean cached) {
		if (cached) {
			StringBuilder sb = new StringBuilder();
			sb.append(clazz.getName());
			sb.append("read");
			sb.append(fieldName);
			String key = sb.toString();
			Method writeMethod = null;
			lock.readLock().lock();
			writeMethod = METHODS.get(key);
			lock.readLock().unlock();
			if (null != writeMethod) {
				return writeMethod;
			}

			lock.writeLock().lock();
			try {
				writeMethod = METHODS.get(sb.toString());
				if (null != writeMethod) {
					return writeMethod;
				}
				PropertyDescriptor pd = new PropertyDescriptor(fieldName, clazz);
				writeMethod = pd.getReadMethod();
				METHODS.put(key, writeMethod);
				return writeMethod;
			} catch (Exception e) {
				throw new RuntimeException(e.getMessage(), e);
			} finally {
				lock.writeLock().unlock();
			}
		} else {
			try {
				PropertyDescriptor pd = new PropertyDescriptor(fieldName, clazz);
				return pd.getReadMethod();
			} catch (IntrospectionException e) {
				throw new RuntimeException(e.getMessage(), e);
			}
		}
	}

	public static Method getWriteMethod(Class<?> clazz, String fieldName, boolean cached) {
		if (cached) {
			StringBuilder sb = new StringBuilder();
			sb.append(clazz.getName());
			sb.append("write");
			sb.append(fieldName);
			String key = sb.toString();
			Method writeMethod = null;
			lock.readLock().lock();
			writeMethod = METHODS.get(key);
			lock.readLock().unlock();
			if (null != writeMethod) {
				return writeMethod;
			}

			lock.writeLock().lock();
			try {
				writeMethod = METHODS.get(sb.toString());
				if (null != writeMethod) {
					return writeMethod;
				}
				PropertyDescriptor pd = new PropertyDescriptor(fieldName, clazz);
				writeMethod = pd.getWriteMethod();
				METHODS.put(key, writeMethod);
				return writeMethod;
			} catch (Exception e) {
				throw new RuntimeException(e.getMessage(), e);
			} finally {
				lock.writeLock().unlock();
			}
		} else {
			try {
				PropertyDescriptor pd = new PropertyDescriptor(fieldName, clazz);
				return pd.getWriteMethod();
			} catch (IntrospectionException e) {
				throw new RuntimeException(e.getMessage(), e);
			}
		}
	}

	@SneakyThrows
	public static Object invokeMethod(Object obj, Method method, Object... args) {
		return method.invoke(obj, args);
	}

	public static Object invokeMethod(Object object, String method, Object... args) {
		MethodAccess access = MethodAccess.get(object.getClass());
		return access.invoke(object, method, args);
	}

	public static void coverFields(Object original, Object shroud) {
		if (!original.getClass().getName().equals(shroud.getClass().getName())) {
			throw new RuntimeException("必须是相同类型的对象");
		}

		Field[] fields = ReflectUtil.getFields(shroud.getClass());
		for (Field field : fields) {
			if(isStatic(field.getModifiers())) {
				continue;
			}
			
			Object shroudVal = ReflectUtil.getFieldValue(shroud, field);
			if (null != shroudVal) {
				ReflectUtil.setFieldValue(original, field.getName(), shroudVal);
				log.info("cover field[{}], value[{}]", field.getName(), shroudVal);
			}
		}
	}

}
